package com.mico.echart.monitor.system;

import com.mico.echart.monitor.model.Processes;
import oshi.hardware.*;
import oshi.software.os.*;
import oshi.util.FormatUtil;
import oshi.util.Util;

import java.util.*;

/**
 * @author micocube
 * projectName: springboot-example
 * packageName: com.mico.echart.monitor.system
 * email: ldscube@gmail.com
 * createTime: 2020-01-15 13:38
 * version: 0.1
 * description:
 */
public class SystemInfo {
    private static final oshi.SystemInfo SYSTEM_INFO = new oshi.SystemInfo();
    private static final HardwareAbstractionLayer HARDWARE = SYSTEM_INFO.getHardware();
    private static final OperatingSystem OS = SYSTEM_INFO.getOperatingSystem();
    private static final GlobalMemory MEMORY = HARDWARE.getMemory();
    private static final ComputerSystem SYSTEM = HARDWARE.getComputerSystem();

    private static long[] prevTicks = HARDWARE.getProcessor().getSystemCpuLoadTicks();
    private static long[][] prevProcTicks = HARDWARE.getProcessor().getProcessorCpuLoadTicks();



    public static List<Processes> getProcess() {

        List<Processes> processesList = new ArrayList<>();
        // Sort by highest CPU

        List<OSProcess> procs = Arrays.asList(OS.getProcesses(100, OperatingSystem.ProcessSort.CPU));

        //"   PID Name  %CPU %MEM       VSZ       RSS "
        for (int i = 0; i < procs.size(); i++) {
            OSProcess p = procs.get(i);
            Processes processes = new Processes(p.getProcessID(), p.getName(),
                    String.format("%5.1f", 100d * (p.getKernelTime() + p.getUserTime()) / p.getUpTime()),
                    String.format("%4.1f", 100d * p.getResidentSetSize() / MEMORY.getTotal()),
                    FormatUtil.formatBytes(p.getVirtualSize()),
                    FormatUtil.formatBytes(p.getResidentSetSize())
            );

            processesList.add(processes);
        }

        return processesList;
    }


    public static Map<String, Object> getComputerSystem() {
        CentralProcessor processor = HARDWARE.getProcessor();

        long[] ticks = processor.getSystemCpuLoadTicks();
        long user = ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()];
        long nice = ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()];
        long sys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()] - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()];
        long idle = ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()];
        long iowait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()] - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()];
        long irq = ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()];
        long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()] - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
        long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()] - prevTicks[CentralProcessor.TickType.STEAL.getIndex()];
        long totalCpu = user + nice + sys + idle + iowait + irq + softirq + steal;

        String cpuUsage = String.format(
                "User: %.1f%% Nice: %.1f%% System: %.1f%% Idle: %.1f%% IOwait: %.1f%% IRQ: %.1f%% SoftIRQ: %.1f%% Steal: %.1f%%",
                100d * user / totalCpu, 100d * nice / totalCpu, 100d * sys / totalCpu, 100d * idle / totalCpu,
                100d * iowait / totalCpu, 100d * irq / totalCpu, 100d * softirq / totalCpu, 100d * steal / totalCpu);
        String cpuLoad = String.format("%.1f%%", processor.getSystemCpuLoadBetweenTicks(prevTicks) * 100);
        double[] loadAverage = processor.getSystemLoadAverage(3);
        String loadAverages = (loadAverage[0] < 0 ? " N/A" : String.format(" %.2f", loadAverage[0]))
                + (loadAverage[1] < 0 ? " N/A" : String.format(" %.2f", loadAverage[1]))
                + (loadAverage[2] < 0 ? " N/A" : String.format(" %.2f", loadAverage[2]));
        // per core CPU
        StringBuilder procCpu = new StringBuilder();
        double[] load = processor.getProcessorCpuLoadBetweenTicks(prevProcTicks);
        for (double avg : load) {
            procCpu.append(String.format(" %.1f%%", avg * 100));
        }



        long freq = processor.getProcessorIdentifier().getVendorFreq();
        final String vendor_Frequency = freq>0?FormatUtil.formatHertz(freq):0+" GHz";


        long maxFreq = processor.getMaxFreq();
        final String max_freq = freq>0?FormatUtil.formatHertz(freq):0+" GHz";


        long[] freqs = processor.getCurrentFreq();
        final StringBuilder sb = new StringBuilder();

        if (freqs[0] > 0) {
            for (int i = 0; i < freqs.length; i++) {
                if (i > 0) {
                    sb.append(", ");
                }
                sb.append(FormatUtil.formatHertz(freqs[i]));
            }
        }


        prevProcTicks = HARDWARE.getProcessor().getProcessorCpuLoadTicks();
        prevTicks = ticks;

        return new HashMap<String,Object>(){{
            put("System",SYSTEM.toString());
            put("Firmware",SYSTEM.getFirmware().toString());
            put("Baseboard",SYSTEM.getBaseboard().toString());
            put("Processor",HARDWARE.getProcessor().toString());
            put("Memory",MEMORY.toString());
            VirtualMemory vm = MEMORY.getVirtualMemory();
            put("Swap",vm.toString());
            PhysicalMemory[] pmArray = MEMORY.getPhysicalMemory();
            if (pmArray.length > 0) {
                StringBuffer buffer = new StringBuffer();
                for (PhysicalMemory pm : pmArray) {
                    buffer.append(" " + pm.toString());
                }
                put("PhysicalMemory",buffer.toString());
            }
            put("CPU_Context_Switches/Interrupts",processor.getContextSwitches() + " / " + processor.getInterrupts());
            put("CPU_Usage",cpuUsage);
            put("CPU_Load",cpuLoad);
            put("CPU_Load_Average",loadAverages);
            put("CPU_Load_Per_Processor",procCpu.toString());
            put("CPU_Vendor_Frequency",vendor_Frequency);
            put("CPU_Max_Frequency",max_freq);
            put("CPU_Current_Frequency",sb.toString());
            put("FileSystem",getFileSystem());
            put("Disks",getDisks());
            put("NetworkParam",getNetworkParameters());
            put("NetworkInterfaces",getNetworkInterfaces());

        }};
    }


    public static Map<String,OSService[]> getServices(){
        OSService[] services = OS.getServices();
        return new HashMap<String,OSService[]>(){{put("services",services);}};
    }


    private static HashMap<String,HWDiskStore[]> getDisks() {
        HWDiskStore[] diskStores = HARDWARE.getDiskStores();
        return new HashMap<String,HWDiskStore[]>(){{put("disks",diskStores);}};

    }

    private static String getFileSystem() {
        FileSystem fileSystem = OS.getFileSystem();
        StringBuffer buffer = new StringBuffer();

        buffer.append(String.format(" File Descriptors: %d/%d", fileSystem.getOpenFileDescriptors(),
                fileSystem.getMaxFileDescriptors()));

        OSFileStore[] fsArray = fileSystem.getFileStores();
        for (OSFileStore fs : fsArray) {
            long usable = fs.getUsableSpace();
            long total = fs.getTotalSpace();
            buffer.append(String.format(
                    " %s (%s) [%s] %s of %s free (%.1f%%), %s of %s files free (%.1f%%) is %s "
                            + (fs.getLogicalVolume() != null && fs.getLogicalVolume().length() > 0 ? "[%s]" : "%s")
                            + " and is mounted at %s",
                    fs.getName(), fs.getDescription().isEmpty() ? "file system" : fs.getDescription(), fs.getType(),
                    FormatUtil.formatBytes(usable), FormatUtil.formatBytes(fs.getTotalSpace()), 100d * usable / total,
                    FormatUtil.formatValue(fs.getFreeInodes(), ""), FormatUtil.formatValue(fs.getTotalInodes(), ""),
                    100d * fs.getFreeInodes() / fs.getTotalInodes(), fs.getVolume(), fs.getLogicalVolume(),
                    fs.getMount()));
        }

        return buffer.toString();
    }

    private static HashMap<String,NetworkIF[]> getNetworkInterfaces() {
        NetworkIF[] networkIFs = HARDWARE.getNetworkIFs();
        return new HashMap<String,NetworkIF[]>(){{put("services",networkIFs);}};
    }

    private static String getNetworkParameters() {
        NetworkParams params = OS.getNetworkParams();
        return params.toString();
    }
}
